﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Text;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using Yahoo.Yui.Compressor;

namespace ECM7.NantContrib.Tasks.Compressor
{
	/// <summary>
	/// Минимизация файлов JS и CSS
	/// </summary>
	/// <typeparam name="TCustomParameters">Специфические параметры</typeparam>
	public abstract class BaseMinifyTask<TCustomParameters> : BaseTask
	{
		#region abstract

		/// <summary>
		/// Тип обрабатываемых файлов для отборажения в логе
		/// </summary>
		protected abstract string FileTypeString { get; }

		/// <summary>
		/// Инициализация
		/// </summary>
		protected abstract TCustomParameters GetCustomParams();

		/// <summary>
		/// Сжатие файлов
		/// </summary>
		/// <param name="content">Содержание минимизируемого файла</param>
		/// <param name="parameters">Специфические параметры сжатия</param>
		/// <param name="encoding">Кодировка</param>
		protected abstract string CompressFile(string content, TCustomParameters parameters, Encoding encoding);

		#endregion

		/// <summary>
		/// Выполнение задачи
		/// </summary>
		protected override void ExecuteTask()
		{
			// инициализация параметров
			var encoding = ParseEncodingType(EncodingType);
			TCustomParameters customParams = GetCustomParams();
			FileSet files = Files ?? new FileSet();

			// проверка корректности параметров
			if (files.FileNames.Count == 0)
			{
				if (DoNotErrorWhenNoFilesAreProvided)
				{
					return;
				}

				throw new BuildException("At least one file is required to be compressed / minified.", Location);
			}

			DateTime now = DateTime.Now;

			LogMessage("Starting compression...");
			WriteCompressorVersion();

			// Сжимаем все файлы и склеиваем в один
			CompressFiles(files.FileNames, customParams, encoding);

			LogMessage("Finished {0} compression.", FileTypeString);
			LogMessage("Total time to execute task: {0}", DateTime.Now - now);
		}

		/// <summary>
		/// Минимизация файлов
		/// </summary>
		/// <param name="files">Список обрабатываемых файлов</param>
		/// <param name="customParams">Дополнительные параметры сжатия</param>
		/// <param name="encoding">Кодировка</param>
		private void CompressFiles(StringCollection files, TCustomParameters customParams, Encoding encoding)
		{
			int initialLength = 0;
			int resultLength = 0;

			// отображение исходных параметров
			LogMessage("# Found one or more {0} file arguments. Now parsing ...", FileTypeString.ToLower());
			LogMessage("# {0} {1} file{2} requested.", files.Count, FileTypeString, files.Count.ToPluralString());

			foreach (string fileName in files)
			{
				try
				{
					LogMessage("Processing file: {0}", fileName);
					string fileContent = File.ReadAllText(fileName);

					string compressedContent = CompressFile(fileContent, customParams, encoding);

					if (!string.IsNullOrEmpty(compressedContent))
					{
						SaveToFile(compressedContent, fileName, encoding);

						initialLength += fileContent.Length;
						resultLength += compressedContent.Length;
					}
				}
				catch
				{
					LogError(
						"Failed to read in the data for the path/file [{0}]. The most common cause " +
						"for this is because the path is incorrect or the file name is incorrect ... " +
						"so please check your path and file names. Until you fix this up, I can't continue.",
						fileName);
				}
			}

			// отображение результата
			WriteFileSizeReport(files.Count, initialLength, resultLength);
		}

		#region helpers

		/// <summary>
		/// Запись в лог версии используемого компрессора
		/// </summary>
		private void WriteCompressorVersion()
		{
			Assembly executingAssembly = typeof(Yahoo.Yui.Compressor.YUICompressor).Assembly;
			object[] customAttributes = executingAssembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false);
			string str = customAttributes.Length > 0
							? ((AssemblyFileVersionAttribute)customAttributes[0]).Version
							: "Unknown File Version";

			object[] objArray2 = executingAssembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
			string str2 = objArray2.Length > 0
							? ((AssemblyTitleAttribute)objArray2[0]).Title
							: "Unknown Title";

			LogMessage("Using version {0} of {1}.", str, str2);
		}

		/// <summary>
		/// Сохранение результата в файл
		/// </summary>
		/// <param name="content">Минимизированный текст</param>
		/// <param name="path">Путь, по которому нужно сохранить файл</param>
		/// <param name="encoding">Кодировка</param>
		protected bool SaveToFile(string content, string path, Encoding encoding)
		{
			try
			{
				File.WriteAllText(path, content, encoding);
				LogMessage("Compressed content saved to file [{0}].{1}", path, Environment.NewLine);
			}
			catch (Exception ex)
			{
				LogError(
					"Failed to save the compressed text into the output file [{0}].  Please check " +
					"the path/file name and make sure the file isn't magically locked, read-only, etc..",
					path);

				throw new BuildException("Error when saving result", Location, ex);
			}

			return true;
		}

		#endregion

		#region Parse parameters

		/// <summary>
		/// Определить кодировку по строке
		/// </summary>
		private static Encoding ParseEncodingType(string encodingType)
		{
			switch ((encodingType ?? string.Empty).ToLowerInvariant())
			{
				case "ascii":
					return Encoding.ASCII;
				case "bigendianunicode":
					return Encoding.BigEndianUnicode;
				case "unicode":
					return Encoding.Unicode;
				case "utf32":
				case "utf-32":
					return Encoding.UTF32;
				case "utf7":
				case "utf-7":
					return Encoding.UTF7;
				case "":
				case "utf8":
				case "utf-8":
					return Encoding.UTF8;
				default:
					return Encoding.Default;
			}
		}

		#endregion

		#region helpers

		/// <summary>
		/// Отображение на экране информации о степени сжатия
		/// </summary>
		/// <param name="count">Количество файлов</param>
		/// <param name="initialSize">Исходный размер</param>
		/// <param name="resultLength">Результирующий размер</param>
		protected void WriteFileSizeReport(int count, int initialSize, int resultLength)
		{
			float rateOfCompression = 100f * resultLength / initialSize;

			LogMessage("Finished compressing all {0} file{1}.", count, count.ToPluralString());
			LogMessage("Total original file size: {0}", initialSize);
			LogMessage("After compression: {0}", resultLength);
			LogMessage("Compressed down to {0}% of original size.", rateOfCompression);
		}

		#endregion

		#region properties

		/// <summary>
		/// Список CSS-файлов для обработки
		/// </summary>
		[BuildElement("files", Required = true)]
		public FileSet Files { get; set; }

		/// <summary>
		/// Не генерировать ошибку, если множество обрабатываемых файлов - пустое
		/// </summary>
		[TaskAttribute("allow-empty-fileset", Required = false), BooleanValidator]
		public bool DoNotErrorWhenNoFilesAreProvided { get; set; }

		/// <summary>
		/// Переносить строки
		/// </summary>
		[TaskAttribute("with-line-breaks", Required = false), BooleanValidator]
		public bool WithLineBreaks { get; set; }

		/// <summary>
		/// Кодировка
		/// </summary>
		public string EncodingType { get; set; }

		#endregion
	}
}
